﻿using System;
using System.Text;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using O1.Kernel.Primitives;

namespace Tests.O1.Kernel.Primitives
{
    [TestClass]
    public class StringPropertyTests
    {
        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyDoesNotThrowWithNullValue()
        {
            // TODO: Handle Null as distinct value.
            Action test = () => StringProperty.Create(123, null);
            test.ShouldNotThrow();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyGetHashCodeWorksWithNullOrEmpty()
        {
            Action test = () => StringProperty.Create(123, null).GetHashCode();
            test.ShouldNotThrow();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyWritesNothingForNullValueAndDoesNotExceedPackedSize()
        {
            var property = StringProperty.Create(123, null);
            var buffer = new byte[StringProperty.FixedSize];
            Action test = () => property.Write(buffer, 0);
            
            test.ShouldNotThrow();
            property.GetSize().Should().Be(StringProperty.FixedSize);
            buffer[StringProperty.FixedSize - 1].Should().Be(0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyCreateThrowsArgumentExceptionWhenStringIsLongerThan15Bytes()
        {
            var value = "1234567890123456";
            Action test = () => StringProperty.Create(1, value);
            test.ShouldThrow<ArgumentException>();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyCreateDoesNotThrowArgumentExceptionWhenStringIs15Bytes()
        {
            var value = "123456789012345";
            Action test = () => StringProperty.Create(1, value);
            test.ShouldNotThrow<ArgumentException>();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyCreateDoesNotThrowArgumentExceptionWhenStringIs15LessThanBytes()
        {
            var value = "12345678901234";
            Action test = () => StringProperty.Create(1, value);
            test.ShouldNotThrow<ArgumentException>();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyCreateDoesNotThrowArgumentExceptionWhenStringIsNull()
        {
            string value = null;
            Action test = () => StringProperty.Create(1, value);
            test.ShouldNotThrow<ArgumentException>();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyCreateDoesNotThrowArgumentExceptionWhenStringIsEmpty()
        {
            var value = string.Empty;
            Action test = () => StringProperty.Create(1, value);
            test.ShouldNotThrow<ArgumentException>();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertySizeIsAsExpected()
        {
            var value = "TTEESSTT";
            var expected = StringProperty.FixedSize + Encoding.UTF8.GetByteCount(value);
            StringProperty.Create(0, value).GetSize().Should().Be(expected);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyWritesDataIntoPackedSizeBufferAsExpected()
        {
            var subject = StringProperty.Create(6, "TESTXYZ");
            var buffer = new byte[subject.GetSize()];
            subject.Write(buffer, 0);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyWritesDataValuesAsExpected()
        {
            var subject = StringProperty.Create(99, "TESTXYZ");
            var buffer = new byte[subject.GetSize()];
            subject.Write(buffer, 0);

            var actualKey = BitConverter.ToInt32(buffer, 0);
            var size = buffer[4];
            var actualValue = Encoding.UTF8.GetString(buffer, 5, size);

            actualValue.Should().Be(subject.Value);
            actualKey.Should().Be(99);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyReadsAsExpected()
        {
            var value = "TESTXYZ";
            var size = (byte)Encoding.UTF8.GetByteCount(value);

            var buffer = new byte[StringProperty.FixedSize + size];

            var key = BitConverter.GetBytes(77);
            var data = Encoding.UTF8.GetBytes(value);

            Array.Copy(key, 0, buffer, 0, 4);
            buffer[4] = size;
            Array.Copy(data, 0, buffer, 5, size);

            var subject = StringProperty.Read(buffer, 0);
            subject.Key.Should().Be(77);
            subject.Value.Should().Be(value);
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyWritesThenReadsAsExpected()
        {
            var expected = StringProperty.Create(99, "TEST");
            var buffer = new byte[expected.GetSize()];
            expected.Write(buffer, 0);

            var actual = StringProperty.Read(buffer, 0);
            actual.Key.Should().Be(expected.Key);
            actual.Value.Should().Be(expected.Value);
            actual.GetSize().Should().Be(expected.GetSize());
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyEqualityIsKeyValueEquality()
        {
            var subject1 = StringProperty.Create(99, "TEST1");
            var subject2 = StringProperty.Create(99, "TEST1");
            (subject1 == subject2).Should().BeTrue();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyInequalityIsKeyInequality()
        {
            var subject1 = StringProperty.Create(99, "TEST1");
            var subject2 = StringProperty.Create(98, "TEST1");
            (subject1 != subject2).Should().BeTrue();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyInequalityIsValueInequality()
        {
            var subject1 = StringProperty.Create(98, "TEST1");
            var subject2 = StringProperty.Create(98, "TEST2");
            (subject1 != subject2).Should().BeTrue();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyEqualsIsValueEquality()
        {
            var subject1 = StringProperty.Create(99, "TEST1");
            var subject2 = StringProperty.Create(99, "TEST1");
            subject1.Equals(subject2).Should().BeTrue();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyEqualsIsKeyInequality()
        {
            var subject1 = StringProperty.Create(99, "TEST1");
            var subject2 = StringProperty.Create(98, "TEST1");
            subject1.Equals(subject2).Should().BeFalse();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyEqualsIsValueInequality()
        {
            var subject1 = StringProperty.Create(98, "TEST1");
            var subject2 = StringProperty.Create(98, "TEST2");
            subject1.Equals(subject2).Should().BeFalse();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyIsNotEqualToNull()
        {
            object other = null;
            StringProperty.Create(98, "TEST").Equals(other).Should().BeFalse();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyGetHashCodeIsConsistent()
        {
            var subject = StringProperty.Create(98, "TEST");
            subject.GetHashCode().Should().Be(subject.GetHashCode());   
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyGetHashCodeIsDifferentForDifferentKey()
        {
            var subject1 = StringProperty.Create(98, "TEST1");
            var subject2 = StringProperty.Create(101, "TEST1");
            subject1.GetHashCode().Should().NotBe(subject2.GetHashCode());
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyGetHashCodeIsDifferentForDifferentValue()
        {
            var subject1 = StringProperty.Create(101, "TEST1");
            var subject2 = StringProperty.Create(101, "TEST2");
            subject1.GetHashCode().Should().NotBe(subject2.GetHashCode());
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyGetHashCodeIsDifferentForDifferentKeyAndValue()
        {
            var subject1 = StringProperty.Create(88, "TEST1");
            var subject2 = StringProperty.Create(101, "TEST2");
            subject1.GetHashCode().Should().NotBe(subject2.GetHashCode());
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyObjectEqualsIsEquality()
        {
            var subject1 = StringProperty.Create(98, "TEST1");
            object subject2 = StringProperty.Create(98, "TEST1");
            subject1.Equals(subject2).Should().BeTrue();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyObjectEqualsIsInequalityForDifferentKey()
        {
            var subject1 = StringProperty.Create(99, "TEST1");
            object subject2 = StringProperty.Create(98, "TEST1");
            subject1.Equals(subject2).Should().BeFalse();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyObjectEqualsIsInequalityForDifferentValue()
        {
            var subject1 = StringProperty.Create(99, "TEST1");
            object subject2 = StringProperty.Create(99, "TEST2");
            subject1.Equals(subject2).Should().BeFalse();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyObjectEqualsIsInequalityForDifferentKeyAndValue()
        {
            var subject1 = StringProperty.Create(8769, "TEST1");
            object subject2 = StringProperty.Create(99, "TEST2");
            subject1.Equals(subject2).Should().BeFalse();
        }

        [TestMethod, TestCategory("Unit Test")]
        public void StringPropertyObjectEqualsIsInequalityForDifferentType()
        {
            var subject1 = StringProperty.Create(99, "TEST1");
            object subject2 = new object();
            subject1.Equals(subject2).Should().BeFalse();
        }
    }
}
